{"values":{"name":"SODROS nedarbingumų importas XML formatu","code":"SS-SODRA-NEDARBINGUMAS","description":null,"triggerTypeId":0,"placeId":"personnel-document-list","scriptContent":"/* ============================\n   CONFIG & INITIAL DATA\n============================ */\n\nconst maxSize = 1000;\n\nconst reportData = {\n    success: [],\n    failed: []\n};\n\nconst personnelDocumentTemplateId = initial.personnelDocumentTemplate;\n\nif(!personnelDocumentTemplateId)\n{\n    throw new UserError(\"Nepasirinktas personalo dokumentų šablonas\")\n}\n\n/* ============================\n   LOAD TEMPLATE\n============================ */\n\nconst personnelDocumentTemplate = await getPersonnelDocumentTemplateById(\n    personnelDocumentTemplateId\n);\n\n/* ============================\n   LOAD & PARSE XML\n============================ */\n\nconst xmlFileContent = await fetchFileData();\nconst parsedJsonData = convertXmlToJson(xmlFileContent);\nlet dataRows = parsedJsonData?.DraudejoAtaskaita?.Eilute ?? [];\n\nif(!Array.isArray(dataRows)) dataRows = [dataRows];\n\n/* ============================\n   FETCH EMPLOYEES\n============================ */\n\nconst uniquePersonalNumbers = [\n    ...new Set(dataRows.map(row => row.AsmensKodas))\n];\n\nconst employees = await getEmployeesByPersonalNumbers(\n    uniquePersonalNumbers,\n    maxSize\n);\n\n// Fast lookup by personal number\nconst employeesMap = new Map(\n  employees.map(emp => [emp.personalNumber, emp])\n);\n\nconst preparedPersonnelDocuments = [];\n\n/* ============================\n   FETCH EXISTING DOCUMENTS\n============================ */\n\nconst certificateNumbers = dataRows.map(entry => entry[\"PazymejimoNumeris\"]);\nconst existingPersonnelDocuments = await getExistingPersonnelDocuments(certificateNumbers, maxSize);\n\n// Fast lookup set for duplicate prevention\nconst existingDocumentsSet = new Set(\n  existingPersonnelDocuments.map(\n    x => `${x.employeeAgreementId}_${x.importReference}`\n  )\n);\n\n/* ============================\n   MATCH EMPLOYEES & PREPARE DOCUMENTS\n============================ */\n\nfor (const dataRow of dataRows) {\n    const employee = employeesMap.get(dataRow[\"AsmensKodas\"]);\n\n    if (employee) {\n        const result = await preparePersonnelDocumentsFromDataRow(\n            dataRow,\n            employee,\n            personnelDocumentTemplate,\n            initial?.date,\n            existingDocumentsSet\n        );\n\n        if (result?.failedEntries?.length) {\n            reportData.failed.push(...result.failedEntries);\n        }\n\n        if (result?.documents?.length) {\n            preparedPersonnelDocuments.push(...result.documents);\n        }\n\n    } else {\n        const message = `Nerastas darbuotojas pagal asmens kodą: ${dataRow[\"AsmensKodas\"]}`;\n        log.warn(message);\n\n        reportData.failed.push({\n            personalNumber: dataRow[\"AsmensKodas\"],\n            firstName: dataRow[\"AsmensVardas\"],\n            lastName: dataRow[\"AsmensPavarde\"],\n            certificateNumber: dataRow[\"PazymejimoNumeris\"],\n            status: \"FAILED\",\n            message,\n            dataRow\n        });\n    }\n}\n\n/* ============================\n   CREATE DOCUMENTS\n============================ */\n\nawait createPersonnelDocumentsAndCollectReport(\n    preparedPersonnelDocuments,\n    reportData\n);\n\n/* ============================\n   GENERATE EXCEL REPORT\n============================ */\n\nlet link = '';\n\nif (reportData.success.length || reportData.failed.length) {\n    const formattedDate = new Date().toISOString().slice(0, 10);\n    const excelFile = await generateExcelReportFromPersonnelImport(reportData);\n\n    const company = await getCompany(companyId);\n\n    const fileUploadResponse = await fileService.uploadFile(\n        excelFile,\n        `Nedarbingumu-importo-ataskaita-${formattedDate}.xlsx`,\n        {\n            fileType: \"ATTACHMENT\",\n            association: {\n                companyId,\n                clientId: company.client.id,\n                clientCode: company.client.code,\n                clientName: company.client.name,\n            },\n            isTemporary: true,\n            generatePublicLink: true\n        }\n    );\n\n    link = `<a href=\"${fileUploadResponse.publicLink}\" target=\"_blank\" style=\"color: green;\">Peržiūrėti ataskaitą</a>`;\n}\n\n/* ============================\n   FINAL STATUS\n============================ */\n\nlet executionStatus;\nlet message;\n\nif (reportData.success.length === 0) {\n    executionStatus = \"FAILED\";\n    message = \"Dokumentų importuoti nepavyko.\";\n} else if (reportData.failed.length > 0) {\n    executionStatus = \"WARNING\";\n    message = `Importuoti ${reportData.success.length} dokumentai, nepavyko importuoti ${reportData.failed.length} dokumentų.`;\n} else {\n    executionStatus = \"SUCCESS\";\n    message = `Sėkmingai importuoti ${reportData.success.length} dokumentai.`;\n}\n\noutput = {\n    executionStatus,\n    message: message + link\n};\n\n/* ============================\n   CREATE PERSONNEL DOCUMENTS\n============================ */\n\nasync function createPersonnelDocumentsAndCollectReport(preparedPersonnelDocuments, context) {\n    for (const personnelDocument of preparedPersonnelDocuments) {\n        try {\n            const createdDocument = await mutate(\"createPersonnelDocument\", {\n                personnelDocument: personnelDocument.personnelDocumentInput\n            });\n\n            const dataRow = personnelDocument.dataRow || {};\n\n            context.success.push({\n                personalNumber: dataRow[\"AsmensKodas\"],\n                firstName: dataRow[\"AsmensVardas\"],\n                lastName: dataRow[\"AsmensPavarde\"],\n                certificateNumber: dataRow[\"PazymejimoNumeris\"],\n                documentNo: createdDocument?.documentNo,\n                employeeAgreementCode: personnelDocument.employeeAgreement.code,\n                status: \"SUCCESS\",\n                message: `Sėkmingai sukurtas dokumentas ${createdDocument?.documentNo}`,\n                dataRow\n            });\n\n        } catch (error) {\n            let message = error?.graphQLErrors?.[0]?.extensions?.rfcException?.violations\n                ?.map(entry => entry.message)\n                .join(', ');\n\n            log.error(message ?? error.message);\n\n            const dataRow = personnelDocument.dataRow || {};\n\n            context.failed.push({\n                personalNumber: dataRow[\"AsmensKodas\"],\n                firstName: dataRow[\"AsmensVardas\"],\n                lastName: dataRow[\"AsmensPavarde\"],\n                certificateNumber: dataRow[\"PazymejimoNumeris\"],\n                status: \"FAILED\",\n                message: message ?? error.message,\n                dataRow\n            });\n        }\n    }\n}\n\n/* ============================\n   XML FETCHING\n============================ */\n\nasync function fetchFileData() {\n\n    if (!initial?.fileName) {\n        throw new UserError(\"Nepasirinktas failas\")\n    }    \n\n    const fileDataResponse = await fetchData(\n        undefined,\n        {\n            url: initial?.fileName?.publicLink,\n            method: \"GET\",\n            //raw: false\n            // Others needed parameters\n        }\n    );\n    return fileDataResponse?.body\n}\n\n/* ============================\n   XML → JSON CONVERSION\n============================ */\n\nfunction convertXmlToJson(xml) {\n    xml = xml.replace(/\\r?\\n|\\t/g, \"\").trim();\n\n    const rootObject = {};\n    const processingStack = [{ obj: rootObject, tag: null }];\n    let currentIndex = 0;\n\n    while (currentIndex < xml.length) {\n        const openingBracketIndex = xml.indexOf(\"<\", currentIndex);\n        if (openingBracketIndex === -1) break;\n\n        const closingBracketIndex = xml.indexOf(\">\", openingBracketIndex);\n        let tagContent = xml.substring(openingBracketIndex + 1, closingBracketIndex).trim();\n\n        const textBetweenTags = xml.substring(currentIndex, openingBracketIndex).trim();\n\n        if (textBetweenTags.length > 0) {\n            const currentNode = processingStack[processingStack.length - 1];\n            currentNode.obj._text = (currentNode.obj._text || \"\") + textBetweenTags;\n        }\n\n        if (tagContent.endsWith(\"/\")) {\n            tagContent = tagContent.slice(0, -1).trim().replace(/^.*:/, \"\");\n            const parentNode = processingStack[processingStack.length - 1].obj;\n            parentNode[tagContent] = \"\";\n            currentIndex = closingBracketIndex + 1;\n            continue;\n        }\n\n        if (tagContent.startsWith(\"/\")) {\n            processingStack.pop();\n            currentIndex = closingBracketIndex + 1;\n            continue;\n        }\n\n        tagContent = tagContent.split(\" \")[0].replace(/^.*:/, \"\");\n        const parentObject = processingStack[processingStack.length - 1].obj;\n\n        let newNode;\n        if (parentObject[tagContent] === undefined) {\n            newNode = {};\n            parentObject[tagContent] = newNode;\n        } else if (!Array.isArray(parentObject[tagContent])) {\n            parentObject[tagContent] = [parentObject[tagContent]];\n            newNode = {};\n            parentObject[tagContent].push(newNode);\n        } else {\n            newNode = {};\n            parentObject[tagContent].push(newNode);\n        }\n\n        processingStack.push({ obj: newNode, tag: tagContent });\n        currentIndex = closingBracketIndex + 1;\n    }\n\n    function cleanParsedObject(object) {\n        if (Array.isArray(object)) return object.map(cleanParsedObject);\n        if (object && typeof object === \"object\") {\n            const objectKeys = Object.keys(object);\n            if (objectKeys.length === 0) return \"\";\n            if (objectKeys.length === 1 && object._text !== undefined) return object._text;\n\n            const cleanedObject = {};\n            for (const key in object) {\n                cleanedObject[key] = cleanParsedObject(object[key]);\n            }\n            return cleanedObject;\n        }\n        return object;\n    }\n\n    return cleanParsedObject(rootObject);\n}\n\n/* ============================\n   PERSONNEL DOCUMENT PREPARATION\n============================ */\n\nasync function preparePersonnelDocumentsFromDataRow(\n    dataRow,\n    employee,\n    personnelDocumentTemplate,\n    documentDate,\n    existingDocumentsSet    \n) {\n    const personnelDocuments = [];\n    const failedEntries = [];\n\n    const timesheetCodeId =\n        personnelDocumentTemplate.timesheetLine.timesheetCode.id;\n\n    const personnelDocumentTemplateId = personnelDocumentTemplate.id;\n\n    const periodStartDate = dataRow[\"LaikotarpisNuo\"].slice(0, 10);\n    const periodEndDate = dataRow[\"LaikotarpisIki\"].slice(0, 10);\n\n    const calendarPeriod = await getCalendarPeriodByDate(periodStartDate);\n    const calendarPeriodId = calendarPeriod?.id;\n\n    for (const employeeAgreement of employee?.employeeAgreements ?? []) {\n\n        const key = `${employeeAgreement.id}_${dataRow[\"PazymejimoNumeris\"]}`;\n\n        // Duplicate check\n        if (existingDocumentsSet.has(key)) {\n            const message = `Pagal sutartį ${employeeAgreement.code} jau yra pažymėjimas`;\n\n            failedEntries.push({\n                personalNumber: dataRow[\"AsmensKodas\"],\n                firstName: dataRow[\"AsmensVardas\"],\n                lastName: dataRow[\"AsmensPavarde\"],\n                certificateNumber: dataRow[\"PazymejimoNumeris\"],\n                status: \"FAILED\",\n                message,\n                dataRow\n            });\n\n            continue;\n        }\n\n        // Agreement not active yet\n        if (employeeAgreement.dateFrom > periodStartDate) continue;\n\n        // Agreement already ended\n        if (employeeAgreement.dateTo && employeeAgreement.dateTo < periodEndDate) continue;\n\n        const personnelDocumentInput = {\n            companyId,\n            employeeAgreementId: employeeAgreement.id,\n            employeeId: employee.id,\n            personnelDocumentTemplateId,\n            opDate: documentDate,\n            notes: dataRow[\"PazymejimoNumeris\"],\n            importReference: dataRow[\"PazymejimoNumeris\"],\n            timesheetLine: {\n                calculationTypeId:\n                    dataRow[\"ArReikiaDraudejuiMoketi\"] === \"Taip\"\n                        ? \"SICK_DAYS_CALCULATION\"\n                        : \"DO_NOT_FILL\",\n                calendarPeriodId,\n                classifierCode: null,\n                classifierTreeCode: null,\n                dateFrom: periodStartDate,\n                dateTo: periodEndDate,\n                timesheetCodeId,\n                timesheetFormationLogicId:\n                    \"SELECT_THE_PERIOD_BASED_ON_THE_START_DATE\",\n                timesheetNote: null\n            },\n            typeId: \"TIMESHEET_EVENT\"\n        };\n\n        personnelDocuments.push({\n            personnelDocumentInput,\n            dataRow,\n            employeeAgreement\n        });\n    }\n\n    return {\n        skipped: failedEntries.length > 0 && personnelDocuments.length === 0,\n        failedEntries,\n        documents: personnelDocuments\n    };\n}\n\n/* ============================\n   EXCEL REPORT GENERATION\n============================ */\n\nasync function generateExcelReportFromPersonnelImport(reportData) {\n    const xlsx = new XLSXBuilder();\n    const sheet = xlsx.addSheet(\"Importo rezultatai\");\n\n    sheet.columnWidth(\"A\", 20);\n    sheet.columnWidth(\"B\", 20);\n    sheet.columnWidth(\"C\", 20);\n    sheet.columnWidth(\"D\", 28);\n    sheet.columnWidth(\"E\", 20);\n    sheet.columnWidth(\"F\", 40);\n    sheet.columnWidth(\"G\", 12);\n    sheet.columnWidth(\"H\", 70);\n    sheet.columnWidth(\"I\", 120);\n\n    sheet.merge(\"A1:I1\");\n    sheet.setCursor(\"A1\");\n    sheet.setStyle({ font: { bold: true, size: 14 } });\n    sheet.append(\"Nedarbingumų importo ataskaita\");\n    sheet.resetStyle();\n\n    sheet.setCursor(\"A3\");\n    sheet.setStyle({ font: { bold: true } });\n    sheet.append([[\n        \"Asmens kodas\",\n        \"Vardas\",\n        \"Pavardė\",\n        \"Pažymėjimo numeris\",\n        \"Dokumento Nr.\",\n        \"Darbo sutartis\",\n        \"Būsena\",\n        \"Pranešimas\",\n        \"Pradiniai duomenys (JSON)\"\n    ]]);\n    sheet.resetStyle();\n\n    let currentRow = 4;\n\n    const allRows = [\n        ...(reportData.success || []),\n        ...(reportData.failed || [])\n    ];\n\n    for (const row of allRows) {\n        sheet.setCursor(`A${currentRow}`);\n        sheet.append([[\n            row.personalNumber ?? \"\",\n            row.firstName ?? \"\",\n            row.lastName ?? \"\",\n            row.certificateNumber ?? \"\",\n            row.documentNo ?? \"\",\n            row.employeeAgreementCode ?? \"\",\n            row.status ?? \"\",\n            row.message ?? \"\",\n            JSON.stringify(row.dataRow ?? {}, null, 2)\n        ]]);\n        currentRow++;\n    }\n\n    return await xlsx.toBuffer();\n}\n\n/* ============================\n   HELPERS\n============================ */\n\nasync function getEmployeesByPersonalNumbers(personalNumbers, maxSize) {\n    const result = await rql(`\n        SELECT id, firstName, lastName, personalNumber,\n               employeeAgreements.id, employeeAgreements.code,\n               employeeAgreements.dateFrom, employeeAgreements.dateTo\n        FROM employeeForPayrolls\n        WITH companyId = ${companyId} size = ${maxSize}\n        WHERE personalNumber = ${toString(personalNumbers, true)}\n    `);\n\n    return result?.content;\n}\n\nasync function getPersonnelDocumentTemplateById(templateId) {\n    const result = await rql(`\n        SELECT id, code, timesheetLine.timesheetCode.id\n        FROM personnelDocumentTemplates\n        WHERE id = ${templateId}\n    `);\n\n    return result.content?.[0];\n}\n\nasync function getCalendarPeriodByDate(date) {\n    const result = await rql(`\n        SELECT * FROM calendarPeriods\n        WHERE dateTo >= DATE('${date}')\n          AND dateFrom <= DATE('${date}')\n    `);\n\n    return result.content?.[0];\n}\n\nasync function getExistingPersonnelDocuments(certificateNumbers, maxSize) {\n    return (await rql(`\n        SELECT employeeAgreementId, importReference\n        FROM personnelDocuments\n        WITH size = ${maxSize} companyId = ${companyId}\n        WHERE importReference = ${toString(certificateNumbers, true)}\n    `))?.content ?? [];\n}\n\nasync function getCompany(companyId) {\n    const result = await rql(`\n        @ALL SELECT client.name, client.address, client.id, client.code\n        FROM companies WHERE id = ${companyId}\n    `);\n    return result?.[0] ?? {};\n}","paramsFormEnabled":true,"paramsFormSchema":"[\n\t\t\t{\n\t\t\"blockType\": \"FIELD\",\n\t\t\"meta\": {\n\t\t\t\"fieldName\": \"date\",\n\t\t\t\"title\": \"jexl:t('date')\",\n\t\t\t\"fieldType\": \"date\",\n\t\t\t\"size\": 3,\n\t\t\t\"required\": true,\n\t\t\t\"meta\": {\n\t\t\t\t\"defaultIsCurrentDate\": true\n\t\t\t}\n\t\t}\n\t},\n\t\t{\n\t\t\t\"blockType\": \"SELECT\",\n\t\t\t\"meta\": {\n\t\t\t\t\"fieldName\": \"personnelDocumentTemplate\",\n\t\t\t\t\"submitFieldName\": \":id\",\n\t\t\t\t\"title\": \"jexl:t('Personalo dokumentų šablonas')\",\n\t\t\t\t\"size\": 3,\n\t\t\t\t\"meta\": {\n\t\t\t\t\t\"getItems\": \"rql:@MDPAGE select id, code, name from personnelDocumentTemplates where personnelDocumentTypeId = 'TIMESHEET_EVENT' AND timesheetLines.timesheetCode.timesheetCodeType =  'SICK_LEAVE'\",\n\t\t\t\t\t\"labelParam\": \"jexl:mdLabel()\",\n\t\t\t\t\t\"isClearable\": true,\n\t\t\t\t\t\"multiple\": false,\n\t\t\t\t\t\"required\": true\n\t\t\t\t}\n\t\t\t}\t\t\n\t\t},\n\t{\n\t\t\"blockType\": \"FILEUPLOAD\",\n\t\t\"meta\": {\n\t\t\t\"fieldName\": \"fileName\",\n\t\t\t\"accept\": [\".xml\"]\n\t\t}\n\t}\t\n\t]","exampleData":"","activeFrom":null,"activeTo":null,"active":true,"appDefinitionId":"90f6ccd4-9411-4efa-aa31-4a8fb9e876a0"},"additionalPlaces":[]}